goog.require('dkpoints.rpc.RPCService');
goog.require('dkpoints.tests.stubFn');

goog.require('goog.json');


var rpc, stubFn, realXhrManager,
    xhrMCtor, // Mock XhrManager constructor
    xhrM; // Mock XhrManager instance.

function setUpPage() {
  stubFn = dkpoints.tests.stubFn;
}

var allAssertsReached; // needed for the integration test.
function setUp() {
  allAssertsReached = true;
  realXhrManager = goog.net.XhrManager;

  xhrM = { send: stubFn() };
  xhrMCtor = goog.net.XhrManager = stubFn(xhrM);
  rpc = new dkpoints.rpc.RPCService();
}


/**
 * Test that the RPCService constructor successfully creates and stores a
 * goog.net.XhrManager and gives it the proper headers.
 */
var testConstructorCreatesXHRManager = function() {
  assertTrue('Constructor should have created a new XhrManager.',
             xhrMCtor.called);
  assertEquals('Headers map needs to include JSON Content Type.',
               'application/json;charset=utf-8',
               xhrMCtor.args[1].get('Content-Type'));
};


/**
 * Test that RPCService.execute calls XhrManager.send with the proper args
 * based on the given Command object.
 */
var testExecuteCallsSendGetWithProperArgs = function() {
  var commandID = 'MockCommand',
      url = '/tests/send/params',
      method = 'GET',
      maxRetries = 26739,
      command = {
        getCommandID: stubFn(commandID),
        url: url,
        method: method,
        maxRetries: maxRetries
      };

  var handler = rpc.handleResponse_ = stubFn();

  rpc.execute(command);

  var args = xhrM.send.args;
  assertNotThrows('args[6] should be the callback and shound not throw.',
                  args[6]);

  assertEquals('First argument to send should have been the commandID.',
               commandID, args[0]);
  assertEquals('Second argument to send should have been the url.',
               url, args[1]);
  assertEquals('Third argument to send should have been the method.',
               method, args[2]);
  assertUndefined('Fourth argument to send should have been undefined.',
                  args[3]);
  assertUndefined('Fifth argument to send should have been undefined.',
                  args[4]);
  assertUndefined('Sixth argument to send should have been undefined.',
                  args[5]);
  // Test for proper handler binding in a separate test.
  assertEquals('Eigth argument to send should have been maxRetries.',
               maxRetries, args[7]);
};


/**
 * Test that the callback given to xhrMgr_.send is properly bound.
 */
var testProperNoCacheCallbackBinding = function() {
  var commandID = 'MockCommand',
      url = '/tests/send/params',
      method = 'GET',
      maxRetries = 26739,
      command = {
        getCommandID: stubFn(commandID),
        url: url,
        method: method,
        maxRetries: maxRetries
      };

  var handler = rpc.handleResponse_ = stubFn();

  rpc.execute(command);

  var args = xhrM.send.args;
  assertNotThrows('7th argument to XhrMgr.send should be rpc.handleResponse_',
                  args[6]); // Executes the callback.

  assertEquals('The first argument to handleResponse_ should have been the ' +
               'commandID.', commandID, handler.args[0]);
};


/**
 * Test that execute caches the command object in rpc.runningCommands_.
 */
var testExecuteCachesCommandObject = function() {
  var commandID = 'MockCommand',
      url = '/tests/send/params',
      method = 'GET',
      maxRetries = 26739,
      command = {
        getCommandID: stubFn(commandID),
        url: url,
        method: method,
        maxRetries: maxRetries
      };

  var handler = rpc.handleResponse_ = stubFn();

  rpc.execute(command);
  assertEquals('RPC.execute should have cached the command object.',
               command, rpc.runningCommands_[commandID]);
};


/**
 * Test that handleResponse silently bails out if no command object is found.
 */
var testHandleResponseExitsIfCommandNotFound = function() {
  var event = {
    target: {
      isSuccess: stubFn()
    }
  };

  rpc.handleResponse_('NonexistentCommandID', event);
  assertFalse('event.target.isSuccess should not have been called.',
              event.target.isSuccess.called);
};


/**
 * Test that handleResponse_ delegates to command.onSuccess for successful
 * XHR responses.
 */
var testHandleRequestDelegatesSuccessfulResponses = function() {
  var commandID = 'MockCommand',
      url = '/tests/send/params',
      method = 'GET',
      maxRetries = 26739,
      command = {
        onSuccess: stubFn(),
        onFailure: stubFn()
      },
      event = {
        target: {
          isSuccess: stubFn(true)
        }
      };

  rpc.runningCommands_[commandID] = command;
  rpc.handleResponse_(commandID, event);

  assertTrue('Command.onSuccess should have been called.',
             command.onSuccess.called);
  assertFalse('Command.onFailure should not have been called.',
              command.onFailure.called);
  assertEquals('Command.onSuccess should have been passed the event object.',
               event, command.onSuccess.args[0]);

  assertUndefined('handleResponse_ should have deleted the reference to the ' +
                  'command object.', rpc.runningCommands_[commandID]);
};


/**
 * Test that handleResponse_ delegates to command.onFailure for failed
 * XHR responses.
 */
var testHandleRequestDelegatesFailedResponses = function() {
  var commandID = 'MockCommand',
      url = '/tests/send/params',
      method = 'GET',
      maxRetries = 26739,
      command = {
        onSuccess: stubFn(),
        onFailure: stubFn()
      },
      event = {
        target: {
          isSuccess: stubFn(false)
        }
      };

  rpc.runningCommands_[commandID] = command;
  rpc.handleResponse_(commandID, event);

  assertFalse('Command.onSuccess should not have been called.',
              command.onSuccess.called);
  assertTrue('Command.onFailure should have been called.',
             command.onFailure.called);
  assertEquals('Command.onFailure should have been passed the event object.',
               event, command.onFailure.args[0]);

  assertUndefined('handleResponse_ should have deleted the reference to the ' +
                  'command object.', rpc.runningCommands_[commandID]);
};


/**
 * Integration test.
 */


/**
 * Test that a request to the server is made, returns, and executes the command
 * object's callback.
 */
var testServerRoundTrip = function() {
  // SetUp mocks goog.net.XhrManager, so un-mock it and create a new
  // rpc service.
  goog.net.XhrManager = realXhrManager;
  rpc = new dkpoints.rpc.RPCService();

  var onSuccess = null,
      onFailure = null,
      commandID = 'RPCService_Integration_Test' + goog.now(),
      url = '/tests/rpc_integration',
      command = new dkpoints.rpc.commands.Command(onSuccess, onFailure,
                                                  commandID, url, 'GET');

  var responseText;
  command.onSuccess = function(event) {
    responseText = event.target.getResponseJson();
  };

  command.onFailure = function(event) {
    fail('Command.onFailure.  Error: ' + goog.json.serialize(event));
  };

  rpc.execute(command);
  allAssertsReached = false;
  waitForCondition(function() { return responseText !== undefined; },
                   function() {
                     allAssertsReached = true;
                     assertTrue('XHR Request did not return true.',
                                responseText); },
                   50, 5000);
};


function tearDown() {
  goog.net.XhrManager = realXhrManager;
  rpc = null;

  if (! allAssertsReached) {
    fail('The final assert in the integration test did not get reached.');
  }
}


var testCase = new goog.testing.ContinuationTestCase(document.title);
testCase.autoDiscoverTests();
G_testRunner.initialize(testCase);
